#include "loader.h"
#include "comm/cpu_instr.h"

/**
 * @brief read disk  specifyed sector and count and copy to  specifyed buf
 *
 * @param sector    : sector of start read
 * @param sector_count  : number of read of sector
 * @param buf       :  data buffer, read and put data in this pointe
 */
static void read_disk(uint32_t sector, uint32_t sector_count, uint8_t *buf)
{
    // setting paramters and read disk
    outb(0x1f6, 0xe0);
    outb(0x1f2, (uint8_t)(sector_count >> 8));
    outb(0x1f3, (uint8_t)(sector >> 24)); // LBA参数的24~31位
    outb(0x1f4, 0x0);                     // LBA参数的32~39位
    outb(0x1f5, 0x0);                     // LBA参数的40~47位

    outb(0x1f2, (uint8_t)(sector_count));
    outb(0x1f3, (uint8_t)(sector));       // LBA参数的0~7位
    outb(0x1f4, (uint8_t)(sector >> 8));  // LBA参数的8~15位
    outb(0x1f5, (uint8_t)(sector >> 16)); // LBA参数的16~23位
    // send the command to read disk
    outb(0x1f7, 0x24);
    // 读取数据
    uint16_t *data_buf = (uint16_t *)buf;
    while (sector_count--)
    {
        // if device busy or data not ready, wait
        while ((inb(0x1f7) & 0x88) != 0x8)
        {
            // wait for
            // here can't use BISO interrupt, so don't print message
        }

        for (int i = 0; i < SECTOR_SIZE / 2; i++)
        {
            *data_buf++ = inw(0x1f0);
        }
    }
}
/**
 * 解析elf文件，提取内容到相应的内存中
 * https://wiki.osdev.org/ELF
 * @param file_buffer
 * @return
 */
static uint32_t reload_elf_file(uint8_t *file_buffer)
{
    Elf32_Ehdr *elf_ehdr = (Elf32_Ehdr *)file_buffer;
    // check the elf file header, if not ok! return 0;
    if ((elf_ehdr->e_ident[0] != 0x7f) || (elf_ehdr->e_ident[1] != 'E') ||
        (elf_ehdr->e_ident[2] != 'L') || (elf_ehdr->e_ident[3] != 'F'))
    {
        return 0;
    }
    // start copy!
    for (int i = 0; i < elf_ehdr->e_phnum; i++)
    {
        Elf32_Phdr *phdr = (Elf32_Phdr *)(file_buffer + elf_ehdr->e_phoff) + i;
        // can't use
        if (phdr->p_type != PT_LOAD)
        {
            continue;
        }
        // start copy!really!
        uint8_t *src = file_buffer + phdr->p_offset;
        uint8_t *dest = (uint8_t *)(phdr->p_paddr);
        for (int i = 0; i < phdr->p_filesz; i++)
        {
            *dest++ = *src++;
        }

        // handle .bss data segment, you should fill with zero
        // when p_memsz > p_filesz, you should fill with zero
        *dest = (uint8_t)phdr->p_paddr + phdr->p_filesz;
        for (int j = 0; j < phdr->p_memsz - phdr->p_filesz; j++)
        {
            *dest++ = 0;
        }
    }

    return elf_ehdr->e_entry;
}

/**
 * 死机
 */
static void die(uint8_t code)
{
    for (;;)
    {
    }
}

// 页面录相关宏定义
#define CR4_PSE (1 << 4)
#define CR0_PG (1 << 31)
#define PDE_P (1 << 0)
#define PDE_W (1 << 1)
#define PDE_PS (1 << 7) // page_size

// 开启单级页目录
// 页目录，32位数组，4k
/**
 * @brief 开启分页机制
 * 将0-4M空间映射到0-4M和SYS_KERNEL_BASE_ADDR~+4MB空间
 * 0-4MB的映射主要用于保护loader自己还能正常工作
 * SYS_KERNEL_BASE_ADDR+4MB则用于为内核提供正确的虚拟地址空间
 */
void enable_page_mode(void)
{
    static uint32_t page_dir[1024] __attribute__((aligned(4096))) = {
        // loader addresss size 4M
        [0] = PDE_P | PDE_W | PDE_PS,
        // [0]=PDE_P|PDE_W|PDE_PS|0x10000000,//映射到错误地址，找不到程序
    };
    uint32_t cr4 = read_cr4();
    // 设置cr4
    write_cr4(cr4 | CR4_PSE);
    write_cr3((uint32_t)page_dir); // 0xa000
    // cr3 寄存器实际上是地址 cr3低12位无效，因此对齐到4k地址处，因此PCD与PWT随便设置为0即可
    // cr0开启分页机制
    write_cr0(read_cr0() | CR0_PG);
}

/**
 * @brief load kernel and jump to kernel module runing, running the OS
 * 从磁盘上加载内核
 */
void load_kernel(void)
{
    // read disk,start 100, read 500 sectors,put above 1M
    read_disk(100, 500, (uint8_t *)SYS_KERNEL_LOAD_ADDR);

    // jump to kernel module
    // ((void (*)(void))SYS_KERNEL_LOAD_ADDR)();

    uint32_t kernel_entry = reload_elf_file((uint8_t *)SYS_KERNEL_LOAD_ADDR);

    // kernel_entry address is error,execute die function and show some message!
    if (kernel_entry == 0)
    {
        die(1);
    }
    // 开启分页机制
    enable_page_mode();

    // carry paramters iump to kernel module
    ((void (*)(boot_info_t *))kernel_entry)(&boot_info);
}
